package com.dream21th.gateway.fliter;

import com.dream21th.common.enums.RtnEnum;
import com.dream21th.common.enums.SafeLevelEnum;
import com.dream21th.common.exception.WebException;
import com.dream21th.common.utils.AesEncryptUtil;
import com.dream21th.common.utils.RSAUtils2;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.Objects;


@Slf4j
@Component
public class SignFilter implements GlobalFilter, Ordered {

    @Value("${key.aes.decode.key}")
    private String aesKey;

    @Value("${key.aes.decode.vi}")
    private String aesVi;

    @Value("${key.rsa.decode.private_key}")
    private String rsaPrivateKey;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        exchange.getAttributes().put("startTime", System.currentTimeMillis());
        HttpHeaders headers = request.getHeaders();
        String safeLevel = headers.getFirst("safe_level");
        if(!StringUtils.equals(SafeLevelEnum.HIGH.code,safeLevel)){
            return chain.filter(exchange);
        }
        if (exchange.getRequest().getMethod().equals(HttpMethod.POST)) {
            //重新构造request，参考ModifyRequestBodyGatewayFilterFactory
            ServerRequest serverRequest = ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders());
            MediaType mediaType = exchange.getRequest().getHeaders().getContentType();

            //重点
            Mono<String> modifiedBody = serverRequest.bodyToMono(String.class).flatMap(body -> {
                //因为约定了终端传参的格式，所以只考虑json的情况，如果是表单传参，请自行发挥
                if (MediaType.APPLICATION_JSON.isCompatibleWith(mediaType) || MediaType.APPLICATION_JSON_UTF8.isCompatibleWith(mediaType)) {
                    JSONObject jsonObject = JSONObject.parseObject(body);

                    //转化成json对象
                    //JSONObject jsonObject = JSONObject.parseObject(bodyJson);
                    String baseReqBody=null;
                    try{
                        String rasMd5=headers.getFirst("decrypt");
                        String md5 = RSAUtils2.decrytDataString(rasMd5, rsaPrivateKey);
                         baseReqBody=jsonObject.getString("a");
                        baseReqBody= AesEncryptUtil.desEncrypt(baseReqBody,aesKey, aesVi).trim();
                        byte[] bytes = baseReqBody.getBytes("UTF-8");
                        String md5DigestAsHex = DigestUtils.md5DigestAsHex(bytes);
                        if(!StringUtils.equals(md5,md5DigestAsHex)){
                            throw new WebException(RtnEnum.SIGN_ERROR);
                        }
                    }catch (Exception e){
                        String code="500";
                        String msg=null;
                        if(e instanceof WebException){
                            code=((WebException) e).getCode();
                            msg=((WebException) e).getMsg();
                        }
                        throw new WebException(code,msg);
                    }

                    return Mono.just(baseReqBody);
                }
                return Mono.empty();
            });
            BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
            HttpHeaders headers1 = new HttpHeaders();
            headers1.putAll(exchange.getRequest().getHeaders());
            //猜测这个就是之前报400错误的元凶，之前修改了body但是没有重新写content length
            headers1.remove("Content-Length");
            //MyCachedBodyOutputMessage 这个类完全就是CachedBodyOutputMessage，只不过CachedBodyOutputMessage不是公共的
            MyCachedBodyOutputMessage outputMessage = new MyCachedBodyOutputMessage(exchange, headers1);
            return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> {
                ServerHttpRequest decorator = this.decorate(exchange, headers1, outputMessage);
                return returnMono(chain, exchange.mutate().request(decorator).build());
            }));
        } else {
            //GET 验签
            return returnMono(chain, exchange);
        }
    }

    @Override
    public int getOrder() {
        return 3;
    }


    private Mono<Void> returnMono(GatewayFilterChain chain,ServerWebExchange exchange){
        return chain.filter(exchange).then(Mono.fromRunnable(()->{
            Long startTime = exchange.getAttribute("startTime");
            if (startTime != null){
                long executeTime = (System.currentTimeMillis() - startTime);
                log.info("耗时：{}ms" , executeTime);
                log.info("状态码：{}" , Objects.requireNonNull(exchange.getResponse().getStatusCode()).value());
            }
        }));
    }


    ServerHttpRequestDecorator decorate(ServerWebExchange exchange, HttpHeaders headers, MyCachedBodyOutputMessage outputMessage) {
        return new ServerHttpRequestDecorator(exchange.getRequest()) {
            public HttpHeaders getHeaders() {
                long contentLength = headers.getContentLength();
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.putAll(super.getHeaders());
                if (contentLength > 0L) {
                    httpHeaders.setContentLength(contentLength);
                } else {
                    httpHeaders.set("Transfer-Encoding", "chunked");
                }
                return httpHeaders;
            }
            public Flux<DataBuffer> getBody() {
                return outputMessage.getBody();
            }
        };
    }

}
